세마포어와 동시성 제어

NOTE

세마포어가 동시성 제어 도구이면서 동시에 IPC로 분류되는 이유, 스레드용 도구와 프로세스용 도구가 갈리는 기준, System V 세마포어 기본 API를 정리한다.

1. 세마포어의 본질: 동시성 제어

세마포어는 원래 여러 실행 흐름이 공유 자원에 접근할 때 순서를 맞추는 카운터/락 메커니즘입니다. 뮤텍스, 조건변수와 같은 계열이라고 보면 됩니다.

2. 근데 왜 IPC로 분류되나

동시성 제어가 필요한 대상이 어디에 있느냐에 따라 도구가 갈립니다.

대상도구IPC 여부
한 프로세스 안의 여러 스레드pthread_mutex, pthread_cond, 이름없는 세마포어(sem_init, pshared=0)아님 — 같은 주소공간이라 그냥 메모리 공유로 해결
서로 다른 프로세스 (독립된 주소공간)System V 세마포어(semget/semop/semctl), named 세마포어(sem_open)맞음 — 커널이 관리하는 공유 리소스에 여러 프로세스가 키/이름으로 접근

프로세스 경계를 넘어서 “락 걸림/풀림” 상태를 주고받는 것 자체가 프로세스 간 통신(정보 교환)의 한 형태라서, System V는 세마포어를 메시지 큐 · 공유메모리와 나란히 IPC 3형제로 묶어놓았습니다.

3. 기본 API

함수역할
semget(key, nsems, flags)세마포어 집합 생성/오픈
semop(semid, ops, nops)P/V 연산(대기/신호) 수행
semctl(semid, semnum, cmd, ...)초기값 설정, 조회, 삭제 등 제어

msgget과 마찬가지로 key 기반으로 자원을 식별한다는 패턴은 동일합니다. → ftok과 IPC 키 생성

4. 락 구현의 종류 — 세마포어는 그중 하나

분류구현쓰는 상황
상호 배제Mutex기본 선택. 못 잡으면 잠들고(커널 sleep/wake), 임계 구역이 길어도 무난
상호 배제Spinlock못 잡으면 루프를 돌며 확인(busy-wait). 임계 구역이 아주 짧거나 잠들 수 없는 곳(커널·인터럽트). 단일 코어에선 낭비
상호 배제재진입(Recursive) 락같은 스레드가 여러 번 잡아야 할 때. 남용하면 설계 문제 신호
읽기/쓰기 구분Reader-Writer Lock (pthread_rwlock_t)읽기는 동시에, 쓰기는 단독. 읽기가 압도적인 테이블·설정. 임계 구역이 짧으면 mutex보다 느릴 수 있음
읽기/쓰기 구분Seqlock읽기는 락 없이 하고, 읽는 중 쓰기가 있었으면 재시도. 쓰기 드물고 데이터 작을 때
개수/신호세마포어동시 접근 N개 제한(자원 풀), 스레드 간 신호 전달
개수/신호Condition Variablemutex와 짝으로 “조건이 될 때까지 대기”
개수/신호Barrier / Latch여러 스레드가 한 지점에서 모이도록 맞춤
락 프리Atomic / CAS (__sync_*, std::atomic)카운터·플래그·포인터 교체
락 프리Lock-free 자료구조, RCU읽기 비용을 거의 없애고 싶을 때(쓰기는 복사 후 교체)
프로세스 간·분산파일 락(flock/fcntl), 공유메모리 mutex, 분산 락(Redis·ZooKeeper·etcd), DB 락프로세스·서버 경계를 넘을 때
선택 순서
  1) 락이 필요 없는 구조인가? (스레드별 데이터 분리, 불변 데이터)
  2) 단순 값이면 atomic
  3) 일반 공유 데이터면 mutex, 읽기 위주면 rwlock(더 극단적이면 RCU/seqlock)
  4) 자원 개수 제한·신호 전달이면 세마포어
  5) 경합이 병목이면 락 분할(샤딩) 또는 한 스레드가 소유하는 구조(메시지 큐)

프로세스 간 락의 함정(사망 시 복구, PTHREAD_PROCESS_SHARED)은 공유메모리 동시성 — 락 선택과 프로세스 간 함정 참고.

5. 세마포어 vs select/poll/epoll — 헷갈리기 쉬운 쌍

둘 다 “기다린다”는 점은 같지만 목적이 다르다.

세마포어select / poll / epoll
목적동기화 — 접근 개수 제한, 스레드 간 신호I/O 다중화 — 여러 fd 중 준비된 것을 기다림
질문”지금 들어가도 되나?""어느 fd에 읽을/쓸 데이터가 생겼나?”
대상카운터 하나fd 여러 개(소켓·파이프·타이머 등)
공유 데이터 보호한다(락의 일종)안 한다(대기만 함)

한 줄 비유: 세마포어는 “들어가도 되는지 허락받는 것”, epoll은 “여러 입구 중 어디에 사람이 왔는지 지켜보는 것”.

두 개념이 만나는 지점: fd 기반 신호

일반 세마포어·조건변수는 epoll로 기다릴 수 없다. 소켓 I/O와 “작업이 들어왔다”는 스레드 간 신호를 한 이벤트 루프에서 같이 기다려야 하면 신호를 fd로 만든다.

수단설명
eventfd커널 카운터 fd. EFD_SEMAPHORE 플래그를 주면 read 1회당 1씩 감소 → 카운팅 세마포어처럼 동작
self-pipe / socketpair다른 스레드가 pipe에 1바이트를 쓰면 select/poll이 깨어남(eventfd 이전의 고전 방식)
timerfd, signalfd타이머 만료·시그널을 fd로 받아 같은 루프에서 처리

→ 자세한 epoll 사용법: epoll — fd가 많아질 때의 대안

핵심

세마포어는 “동시성 제어 도구”이면서 동시에 “IPC”입니다 — 프로세스 경계를 넘어 동작할 때만 IPC로 분류되고, 한 프로세스 내부(스레드 간)에서는 그냥 동시성 도구일 뿐입니다. 락 구현은 세마포어 외에도 mutex·spinlock·rwlock·atomic 등이 있고, select/epoll은 락이 아니라 I/O 대기 도구입니다.

관련 문서